package vehicle

import (
	"bytes"
	"errors"
	"gitee.com/watertreestar/octans-device-sdk/internal"
	"gitee.com/watertreestar/octans-device-sdk/log"
	"gitee.com/watertreestar/octans-device-sdk/shadow"
	"github.com/jasonlvhit/gocron"
	"github.com/lunixbochs/struc"
	"github.com/patrickmn/go-cache"
	"net"
	"strconv"
	"strings"
	"time"
)

var (
	DeviceNotFound       = errors.New("device not found")
	SNNotValidError      = errors.New("sn text is not valid")
	DeviceNotOnlineError = errors.New("device is not online")
)

type controlDevice struct {
	sn        string
	serialNum uint32
	mac       string
	ip        string
}

type SearchedDevice struct {
	SerialNum uint32 `json:"serial_num"`
	Mac       string `json:"mac"`
	IP        string `json:"ip"`
}

// Connector 蓝牙控制卡连接器，用于控制车行门禁
type Connector struct {
	devices        []controlDevice
	listener       *net.UDPConn
	port           int
	limiterCache   *cache.Cache
	swipeEventChan chan SwipeData
	logger         log.Logger
	s              *gocron.Scheduler
}

// NewConnector 创建蓝牙控制卡连接器
// port-UDP服务监听的端口，用于接收刷卡事件
func NewConnector(port int, logger log.Logger) *Connector {
	return &Connector{
		port:           port,
		swipeEventChan: make(chan SwipeData),
		logger:         logger,
	}
}

func (c *Connector) Initialize() error {
	// 卡在扫描范围内会多次触发事件，用来防抖动
	c.limiterCache = cache.New(time.Second*30, time.Minute)
	c.s = gocron.NewScheduler()
	c.s.Start()
	c.tickHello()
	if err := c.serve(); err != nil {
		return err
	}
	c.logger.Info("vehicle control udp server started at: %d ", c.port)
	return nil
}

func (c *Connector) Destroy() error {
	if c.listener != nil {
		_ = c.listener.Close()
	}
	return nil
}

func (c *Connector) GetSwipeEvent() chan SwipeData {
	return c.swipeEventChan
}

// AddDevice 添加设备
// sn 唯一任意值
// serialNum 控制卡的序列号
// ip 控制卡IP
// port 控制卡port，一般为60000
func (c *Connector) AddDevice(sn string, serialNum uint32, ip string) error {
	if len(strings.TrimSpace(sn)) == 0 {
		return SNNotValidError
	}

	if err := shadow.Of().AddDevice(sn, time.Minute*3); err != nil {
		return err
	}
	c.devices = append(c.devices, controlDevice{
		sn:        sn,
		serialNum: serialNum,
		ip:        ip,
	})
	return nil
}

func (c *Connector) RemoveDevice(sn string) error {
	if err := shadow.Of().RemoveDevice(sn); err != nil {
		return err
	}
	devices := c.devices
	var filter []controlDevice
	for _, d := range devices {
		if d.sn != sn {
			filter = append(filter, d)
		}
	}
	c.devices = filter
	return nil
}

// AddPrivilege 添加卡
func (c *Connector) AddPrivilege(sn string, cardNum uint32) error {
	online, err := shadow.Of().GetStatus(sn)
	if err != nil {
		return err
	}
	if !online {
		return DeviceNotOnlineError
	}
	for _, d := range c.devices {
		if d.sn == sn {
			packet := UploadPrivilegePacket{}
			packet.Magic = []byte{0x17, 0x50}
			packet.SN = d.serialNum
			packet.CardNum = cardNum
			packet.permitAll()
			buf := bytes.Buffer{}
			if err := struc.Pack(&buf, &packet); err != nil {
				return err
			}
			return sendUDPPacket(d.ip, 60000, buf.Bytes())
		}
	}
	return DeviceNotFound
}

// RemovePrivilege 删除卡号
func (c *Connector) RemovePrivilege(sn string, cardNum uint32) error {
	online, err := shadow.Of().GetStatus(sn)
	if err != nil {
		return err
	}
	if !online {
		return DeviceNotOnlineError
	}
	for _, d := range c.devices {
		if d.sn == sn {
			packet := RemovePrivilegePacket{}
			packet.Magic = []byte{0x17, 0x52}
			packet.SN = d.serialNum
			packet.CardNum = cardNum
			buf := bytes.Buffer{}
			if err := struc.Pack(&buf, &packet); err != nil {
				return err
			}
			return sendUDPPacket(d.ip, 60000, buf.Bytes())
		}
	}
	return DeviceNotFound
}

// ClearPrivilege 清空卡号
func (c *Connector) ClearPrivilege(sn string) error {
	online, err := shadow.Of().GetStatus(sn)
	if err != nil {
		return err
	}
	if !online {
		return DeviceNotOnlineError
	}
	for _, d := range c.devices {
		if d.sn == sn {
			packet := ClearPrivilegePacket{}
			packet.Magic = []byte{0x17, 0x54}
			packet.SN = d.serialNum
			packet.Flag = []byte{0x55, 0xAA, 0xAA, 0x55}
			buf := bytes.Buffer{}
			if err := struc.Pack(&buf, &packet); err != nil {
				return err
			}
			return sendUDPPacket(d.ip, 60000, buf.Bytes())
		}
	}
	return DeviceNotFound
}

func (c *Connector) Discovery() (device []SearchedDevice, err error) {
	var remotePort = 60000
	var discoveryPacket = make(chan *bytes.Buffer)
	ifaces, _ := internal.FilterInterfaces(true)

	conn, err := net.ListenUDP("udp4", &net.UDPAddr{
		IP:   net.IPv4(0, 0, 0, 0),
		Port: 8999,
	})
	if err != nil {
		return
	}
	defer func() {
		_ = conn.Close()
	}()
	go recvPacket(conn, discoveryPacket)
	go broadcast(ifaces, remotePort)

	// 等待广播包响应
	var d []SearchedDevice
	for buffer := range discoveryPacket {
		var resp DiscoveryResponse
		if err := struc.Unpack(buffer, &resp); err != nil {
			continue
		}
		if resp.Magic[0] == 0x17 && resp.Magic[1] == 0x94 {
			d = append(d, SearchedDevice{
				SerialNum: resp.SN,
				Mac:       resp.MacString(),
				IP:        resp.IPString(),
			})
		}
	}
	return d, nil
}

func (c *Connector) serve() error {
	l, err := net.ListenUDP("udp", &net.UDPAddr{
		IP:   net.IPv4(0, 0, 0, 0),
		Port: c.port,
	})
	if err != nil {
		return err
	}
	c.listener = l
	go func() {
		for {
			var buf = make([]byte, 64)
			_, _, err := l.ReadFromUDP(buf)
			if err != nil {
				continue
			}
			go c.handle(buf)
		}
	}()
	return nil
}

func (c *Connector) handle(data []byte) {
	payload := &RecordResponse{}
	err := struc.Unpack(bytes.NewReader(data), payload)
	if err != nil {
		return
	}
	serialNum := payload.SN
	// get sn by serial number
	var sn = ""
	for _, d := range c.devices {
		if d.serialNum == serialNum {
			sn = d.sn
		}
	}
	if len(sn) == 0 {
		c.logger.Warn("device sn not found by serial number", serialNum)
		return
	}
	if payload.RecordType == 1 {
		numtext := strconv.FormatUint(uint64(payload.CardNum), 10)
		if _, ok := c.limiterCache.Get(numtext); ok {
			return
		}
		c.limiterCache.Set(numtext, "", cache.DefaultExpiration)
		c.swipeEventChan <- SwipeData{
			SN:        sn,
			Pass:      payload.Pass,
			DoorNum:   payload.DoorNum,
			Direction: payload.Direction,
			CarNum:    payload.CardNum,
		}
	}
}

func sendUDPPacket(remoteIP string, remotePort int, data []byte) error {
	remoteAddr := &net.UDPAddr{IP: net.ParseIP(remoteIP), Port: remotePort}
	conn, err := net.DialUDP("udp4", nil, remoteAddr)
	if err != nil {
		return err
	}
	if _, err := conn.Write(data); err != nil {
		return err
	}
	var buffer = make([]byte, 64)
	_ = conn.SetReadDeadline(time.Now().Add(time.Second * 5))
	if _, _, err := conn.ReadFromUDP(buffer); err != nil {
		return err
	}
	return nil
}

func sendProbeStatusPacket(serialNum uint32, ip string) error {
	var packet = probeStatusPacket{}
	packet.Magic = []byte{0x17, 0x24}
	packet.SN = serialNum
	var buf = bytes.Buffer{}
	if err := struc.Pack(&buf, &packet); err != nil {
		return err
	}

	return sendUDPPacket(ip, 60000, buf.Bytes())
}

func (c *Connector) probeStatus() {
	var request = make([]byte, 64)
	request[0] = 0x17
	request[1] = 0x24
	for _, d := range c.devices {
		if err := sendProbeStatusPacket(d.serialNum, d.ip); err != nil {
			continue
		}
		_ = shadow.Of().RefreshUpdateAt(d.sn)
	}
}

// 定时发送获取状态的请求到设备，目的：
// 1. 新设置服务器接受IP的设备需要服务器主动发送一次消息后才能接收到刷卡事件
// 2. 检测设备是否存活
func (c *Connector) tickHello() {
	if err := c.s.Every(10).Seconds().Do(c.probeStatus); err != nil {
		c.logger.Error("failed to start status tick task")
	}
}

// 接收广播包,最长等待10s
func recvPacket(conn *net.UDPConn, packetChan chan *bytes.Buffer) {
	t := time.NewTimer(time.Second * 2)
	for {
		select {
		case <-t.C:
			close(packetChan)
			return
		default:
			var buffer = make([]byte, 64)
			_ = conn.SetReadDeadline(time.Now().Add(time.Millisecond * 100))
			_, _, err := conn.ReadFromUDP(buffer)
			if err != nil {

			} else {
				packetChan <- bytes.NewBuffer(buffer)
			}
		}
	}
}

func broadcast(ifaces []net.Interface, remotePort int) {
	broadcastAddr := &net.UDPAddr{IP: net.ParseIP("255.255.255.255"), Port: remotePort}
	var discoveryPayload = make([]byte, 64)
	discoveryPayload[0] = 0x17
	discoveryPayload[1] = 0x94

	for _, iface := range ifaces {
		addrs, err := iface.Addrs()
		if err != nil {
			continue
		}
		for _, ad := range addrs {
			if ipnet, ok := ad.(*net.IPNet); ok && !ipnet.IP.IsLoopback() && ipnet.IP.To4() != nil {
				local := &net.UDPAddr{IP: net.ParseIP(ipnet.IP.String()), Port: 8999}
				conn, err := net.DialUDP("udp4", local, broadcastAddr)
				if err != nil {
					continue
				}
				if _, err = conn.Write(discoveryPayload); err != nil {
					// log
				}
				_ = conn.Close()
			}
		}
	}
}
